
#include "nane/FileArchiveSystem/ZIP/ZIPFileArchive.h"
#include "nane/FileArchiveSystem/ZIP/ZIPFileDeflated.h"
#include "nane/FileArchiveSystem/ZIP/ZIPFileUncompressed.h"
#include "nane/core/IO.h"
#include "nane/core/log.h"
#include "nane/core/crt/filesystem.h"

#include <vector>

namespace nane
{
    ////////////////////////////////////////////////////////////////////////////////////////////////
    static const uint32 c_zipFileHeader = 0x04034b50;
    static const uint32 c_zipDataDescriptorHeader = 0x08074b50;
    static const uint32 c_zipCentralDirectoryHeader = 0x02014b50;
    ////////////////////////////////////////////////////////////////////////////////////////////////
    ZIPFileArchive::ZIPFileArchive()
        : m_input( NullPtr )
    {
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    ZIPFileArchive::~ZIPFileArchive()
    {
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool ZIPFileArchive::Initialize( const InputSeekablePtr& _input )
    {
        while( true )
        {
            uint32 header = 0;
            uint16 version = 0;
            uint16 bitflag = 0;
            uint16 method = 0;
            uint16 modTime = 0;
            uint16 modDate = 0;
            uint32 crc32 = 0;
            uint32 csize = 0;
            uint32 usize = 0;
            uint16 filenameLen = 0;
            uint16 extraLen = 0;
            std::vector<byte> filename;
            
            IInputSeekableRef* input = _input.Get();
            if( IO<IOUnsafe>::Read(input, &header) != sizeof(header) )
            {
                break;
            }
            if( header == c_zipCentralDirectoryHeader )
            {
                // we have all info already
                break;
            }
            else if( header != c_zipFileHeader )
            {
                continue;
            }
            if( IO<IOUnsafe>::Read(input, &version) != sizeof(version)
                || IO<IOUnsafe>::Read(input, &bitflag) != sizeof(bitflag)
                || IO<IOUnsafe>::Read(input, &method) != sizeof(method)
                || IO<IOUnsafe>::Read(input, &modTime) != sizeof(modTime)
                || IO<IOUnsafe>::Read(input, &modDate) != sizeof(modDate)
                || IO<IOUnsafe>::Read(input, &crc32) != sizeof(crc32)
                || IO<IOUnsafe>::Read(input, &csize) != sizeof(csize)
                || IO<IOUnsafe>::Read(input, &usize) != sizeof(usize)
                || IO<IOUnsafe>::Read(input, &filenameLen) != sizeof(filenameLen)
                || IO<IOUnsafe>::Read(input, &extraLen) != sizeof(extraLen) )
            {
                break;
            }
            filename.resize(filenameLen + 1, 0);
            input->Read(&(filename[0]), filenameLen);
            const char* cFilename = reinterpret_cast<const char*>(&filename[0]);
            bool hasDataDescriptor = (bitflag & (1 << 3)) != 0;
            if( IO<IOUnsafe>::Skip(input, extraLen) != extraLen )
            {
                break;
            }
            
            if( method != 0 && method != 8 )    // unknown compression
            {
                LOG_WARNING("File '%1' has unknown compression method in zip archive") << cFilename;
            }
            else if( csize != 0 )
            {
                ZIPFileInfo zipInfo = { (method == 8), input->Tell(), csize, usize };
                std::string zipFilename(reinterpret_cast<const char*>(&filename[0]));
                m_zipMap.insert(std::make_pair(zipFilename, zipInfo));
                IO<IOUnsafe>::Skip(input, csize);
            }
            if( hasDataDescriptor != false )
            {
                uint32 header = 0;
                IO<IOUnsafe>::Read(input, &header);
                uint32 skipBytes = header == c_zipDataDescriptorHeader ? 12 : 8;
                IO<IOUnsafe>::Skip(input, skipBytes);
            }
        }
        m_input = _input;
        return true;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void ZIPFileArchive::Shutdown()
    {
        m_zipMap.clear();
        m_input = NullPtr;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    IReadFile* ZIPFileArchive::OpenFile( const char* _filename )
    {
        ZIPFileBase* zipFile = NULL;
        TZIPMap::iterator it_find = m_zipMap.find(_filename);
        if( it_find != m_zipMap.end() )
        {
            ZIPFileInfo& zipInfo = it_find->second;
            if( zipInfo.compressed == true )
            {
                ZIPFileDeflated* zipFileDeflated = new ZIPFileDeflated(this, m_input, zipInfo.beginPos, zipInfo.size, zipInfo.uncompressedSize);
                if( zipFileDeflated->Initialize() == false )
                {
                    delete zipFileDeflated;
                    zipFileDeflated = NULL;
                }
                zipFile = static_cast<ZIPFileBase*>(zipFileDeflated);
            }
            else
            {
                zipFile = new ZIPFileUncompressed(this, m_input, zipInfo.beginPos, zipInfo.size);
            }
        }
        return zipFile;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void ZIPFileArchive::Release( ZIPFileDeflated* _zipFileDeflated )
    {
        delete _zipFileDeflated;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void ZIPFileArchive::Release( ZIPFileUncompressed* _zipFileUncompressed )
    {
        delete _zipFileUncompressed;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
}   // namespace nane
